// ... and this variant isn't suspended (we already know IsCompletelyDisabled()==false from an earlier check).
{
hot.mType = HK_NORMAL; // Override the default. Hook not needed.
break;
}
}
// If the above promoted it from NORMAL to HOOK but the hotkey is currently registered,
// it will be unregistered later below.
}
}
// Check if this mouse hotkey also requires the keyboard hook (e.g. #LButton).
// Some mouse hotkeys, such as those with normal modifiers, don't require it
// since the mouse hook has logic to handle that situation. But those that
// are composite hotkeys such as "RButton & Space" or "Space & RButton" need
// the keyboard hook:
if (hot.mType == HK_MOUSE_HOOK && (
hot.mModifierSC || hot.mSC // i.e. since it's an SC, the modifying key isn't a mouse button.
|| hot.mHookAction // v1.0.25.05: At least some alt-tab actions require the keyboard hook. For example, a script consisting only of "MButton::AltTabAndMenu" would not work properly otherwise.
// v1.0.25.05: The line below was added to prevent the Start Menu from appearing, which
// requires the keyboard hook. ALT hotkeys don't need it because the mouse hook sends
// a CTRL keystroke to disguise them, a trick that is unfortunately not reliable for
// when it happens while the while key is down (though it does disguise a Win-up).
// I know this isn't the preferred way to exit the program. However, due to unusual
// conditions such as the script having MsgBoxes or other dialogs displayed on the screen
// at the time the user exits (in which case our main event loop would be "buried" underneath
// the event loops of the dialogs themselves), this is the only reliable way I've found to exit
// so far. The caller has already called PostQuitMessage(), which might not help but it doesn't hurt:
exit(aExitCode); // exit() is insignificant in code size. It does more than ExitProcess(), but perhaps nothing more that this application actually requires.
// By contrast to _exit(), exit() flushes all file buffers before terminating the process. It also
// calls any functions registered via atexit or _onexit.
// The following check is for maintainability, since caller should have already checked and
// handled HOTKEY_ID_ALT_TAB and similar. Less-than-zero check not necessary because it's unsigned.
if (hotkey_id >= sHotkeyCount)
return false; // Special alt-tab hotkey quasi-ID used by the hook.
Hotkey &hk = *shk[hotkey_id]; // For convenience and performance.
if (aFireWithNoSuppress // Caller has already determined its value with certainty...
|| (hk.mNoSuppress & NO_SUPPRESS_SUFFIX_VARIES) != NO_SUPPRESS_SUFFIX_VARIES) // ...or its value is easy to determine, so do it now (compare to itself since it's a bitwise union).
{
// Since aFireWithNoSuppress can now be easily determined for the caller (or was already determined by the caller
// itself), it's possible to take advantage of the following optimization, which is especially important in cases
// where TitleMatchMode is "slow":
// For performance, the following returns without having called WinExist/Active if it sees that one of this
// hotkey's variant's will certainly fire due to the fact that it has a non-suspended global variant.
// This reduces the number of situations in which double the number of WinExist/Active() calls are made
// (once in the hook to determine whether the hotkey keystroke should be passed through to the active window,
// and again upon receipt of the message for reasons explained there).
for (HotkeyVariant *vp = hk.mFirstVariant; vp; vp = vp->mNextVariant)
if (!vp->mHotCriterion && vp->mEnabled && (!g_IsSuspended || vp->mJumpToLabel->IsExemptFromSuspend()))
{
// Fix for v1.0.47.02: The following section (above "return") was moved into this block
// from above the for() because only when this for() returns is it certain that this
// hk/hotkey_id is actually the right one, and thus its attributes can be used to determine
// aFireWithNoSuppress for the caller.
// Since this hotkey has variants only of one type (tilde or non-tilde), this variant must be of that type.
if (!aFireWithNoSuppress) // Caller hasn't yet determined its value with certainty (currently, this statement might always be true).
aFireWithNoSuppress = (hk.mNoSuppress & AT_LEAST_ONE_VARIANT_HAS_TILDE); // Due to other checks, this means all variants are tilde.
return true;
}
}
// Since above didn't return, a slower method is needed to find out which variant of this hotkey (if any)
// should fire.
HotkeyVariant *vp;
if (vp = hk.CriterionAllowsFiring())
{
if (!aFireWithNoSuppress) // Caller hasn't yet determined its value with certainty (currently, this statement might always be true).
aFireWithNoSuppress = vp->mNoSuppress;
return true; // It found an eligible variant to fire.
}
// Since above didn't find any variant of the hotkey than can fire, check for other eligible hotkeys.
if (!(hk.mModifierVK || hk.mModifierSC || hk.mHookAction)) // Rule out those that aren't susceptible to the bug due to their lack of support for wildcards.
{
// Fix for v1.0.46.13: Although the section higher above found no variant to fire for the
// caller-specified hotkey ID, it's possible that some other hotkey (one with a wildcard) is
// eligible to fire due to the eclipsing behavior of wildcard hotkeys. For example:
// #IfWinNotActive Untitled
// q::tooltip %A_ThisHotkey% Non-notepad
// #IfWinActive Untitled
// *q::tooltip %A_ThisHotkey% Notepad
// However, the logic here might not be a perfect solution because it fires the first available
// hotkey that has a variant whose criteria are met (which might not be exactly the desired rules
// of precedence). However, I think it's extremely rare that there would be more than one hotkey
// that matches the original hotkey (VK, SC, has-wildcard) etc. Even in the rare cases that there
// is more than one, the rarity is compounded by the rarity of the bug even occurring, which probably
// makes the odds vanishingly small. That's why the following simple, high-performance loop is used
// rather than more a more complex one that "locates the smallest (most specific) eclipsed wildcard
// hotkey", or "the uppermost variant among all eclipsed wildcards that is eligible to fire".
for (int i = 0; i < sHotkeyCount; ++i) // This loop is undesirable; but its performance impact probably isn't measurable in the majority of cases.
{
Hotkey &hk2 = *shk[i]; // For performance and convenience.
if ( hk2.mVK == hk.mVK // VK and SC (one of which is typically zero) must both match for
&& hk2.mSC == hk.mSC // this bug to have wrongly eclipsed a qualified variant of some other hotkey.
&& hk2.mAllowExtraModifiers // To be eclipsable by the original hotkey, a candidate must have a wildcard.
&& hk2.mKeyUp == aKeyUp // Seems necessary that up/down nature is the same in both.
&& !hk2.mModifierVK // Avoid accidental matching of normal hotkeys with custom-combo "&"
&& !hk2.mModifierSC // hotkeys that happen to have the same mVK/SC.
&& !hk2.mHookAction // Might be unnecessary to check this; but just in case.
&& hk2.mID != hotkey_id // Don't consider the original hotkey because it's was already found ineligible.
&& (hk.mAllowExtraModifiers // Either the original hotkey must allow extra modifiers or the candidate must not have any modifiers present on the original (otherwise the user probably isn't holding down the right keys to trigger this hotkey).
|| !((hk.mModifiersConsolidatedLR ^ hk2.mModifiersConsolidatedLR) & hk2.mModifiersConsolidatedLR)) // "The modifiers that are different intersected with those present on the candidate", which are those present on the candidate that are absent from this original.
//&& hk2.mType != HK_JOYSTICK // Seems unnecessary since joystick hotkeys don't call us and even if they did, probably should be included.
//&& hk2.mParentEnabled ) // CriterionAllowsFiring() will check this for us.
)
{
// The following section is similar to one higher above, so maintain them together:
if (vp = hk2.CriterionAllowsFiring())
{
if (!aFireWithNoSuppress) // Caller hasn't yet determined its value with certainty (currently, this statement might always be true).
aFireWithNoSuppress = vp->mNoSuppress;
aHotkeyIDwithFlags = hk2.mID; // Caller currently doesn't need the flags put onto it, so they're omitted.
return true; // It found an eligible variant to fire.
}
}
}
}
// Otherwise, this hotkey has no variants that can fire. Caller wants a few things updated in that case.
if (!aFireWithNoSuppress) // Caller hasn't yet determined its value with certainty.
aFireWithNoSuppress = true; // Fix for v1.0.47.04: Added this line and the one above to fix the fact that a context-sensitive hotkey like "a UP::" would block the down-event of that key even when the right window/criteria aren't met.
// If this is a key-down hotkey:
// Leave aHotkeyToFireUponRelease set to whatever it was so that the critieria are
// evaluated later, at the time of release. It seems more correct that way, though the actual
// change (hopefully improvement) in usability is unknown.
// Since the down-event of this key won't be suppressed, it seems best never to suppress the
// key-up hotkey (if it has one), if nothing else than to be sure the logical key state of that
// key as shown by GetAsyncKeyState() returns the correct value (for modifiers, this is even more
// important since them getting stuck down causes undesirable behavior). If it doesn't have a
// key-up hotkey, the up-keystroke should wind up being non-suppressed anyway due to default
// processing).
if (!aKeyUp)
aNoSuppress |= NO_SUPPRESS_NEXT_UP_EVENT; // Update output parameter for the caller.
if (aSingleChar)
*aSingleChar = '#'; // '#' in KeyHistory to indicate this hotkey is disabled due to #IfWin criterion.
// Below relies on the fact that either variant or hk->mHookAction (or both) is now non-zero.
// Specifically, when an existing hotkey was changed to become an alt-tab hotkey, above, there will sometimes
// be a NULL variant (depending on whether there happens to be a variant in the hotkey that matches the current criteria).
// If aOptions is blank, any new hotkey or variant created above will have used the current values of
// g_MaxThreadsBuffer, etc.
if (*aOptions)
{
for (char *cp = aOptions; *cp; ++cp)
{
switch(toupper(*cp))
{
case 'O': // v1.0.38.02.
if (toupper(cp[1]) == 'N') // Full validation for maintainability.
{
++cp; // Omit the 'N' from further consideration in case it ever becomes a valid option letter.
if (hk->mHookAction ? hk->EnableParent() : hk->Enable(*variant)) // Under these conditions, earlier logic has ensured variant is non-NULL.
update_all_hotkeys = true; // Do it this way so that any previous "true" value isn't lost.
}
else if (!strnicmp(cp, "Off", 3))
{
cp += 2; // Omit the letters of the word from further consideration in case "f" ever becomes a valid option letter.
if (hk->mHookAction ? hk->DisableParent() : hk->Disable(*variant)) // Under these conditions, earlier logic has ensured variant is non-NULL.
update_all_hotkeys = true; // Do it this way so that any previous "true" value isn't lost.
if (variant_was_just_created) // This variant (and possibly its parent hotkey) was just created above.
update_all_hotkeys = false; // Override the "true" that was set (either right above *or* anywhere earlier) because this new hotkey/variant won't affect other hotkeys.
}
break;
case 'B':
if (variant)
variant->mMaxThreadsBuffer = (cp[1] != '0'); // i.e. if the char is NULL or something other than '0'.
break;
// For options such as P & T: Use atoi() vs. ATOI() to avoid interpreting something like 0x01B
// as hex when in fact the B was meant to be an option letter:
case 'P':
if (variant)
variant->mPriority = atoi(cp + 1);
break;
case 'T':
if (variant)
{
variant->mMaxThreads = atoi(cp + 1);
if (variant->mMaxThreads > MAX_THREADS_LIMIT)
// For now, keep this limited to prevent stack overflow due to too many pseudo-threads.
variant->mMaxThreads = MAX_THREADS_LIMIT;
else if (variant->mMaxThreads < 1)
variant->mMaxThreads = 1;
}
break;
case 'U':
// The actual presence of "UseErrorLevel" was already acted upon earlier, so nothing
// is done here other than skip over the string so that the letters inside it aren't
// misinterpreted as other option letters.
if (!stricmp(cp, "UseErrorLevel"))
cp += 12; // Omit the rest of the letters in the string from further consideration.
break;
// Otherwise: Ignore other characters, such as the digits that comprise the number after the T option.
}
} // for()
} // if (*aOptions)
if (update_all_hotkeys)
ManifestAllHotkeysHotstringsHooks(); // See its comments for why it's done in so many of the above situations.
// Somewhat debatable, but the following special ErrorLevels are set even if the above didn't
// need to re-manifest the hotkeys.
if (use_errorlevel)
{
g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Set default, possibly to be overridden below.
if (HK_TYPE_IS_HOOK(hk->mType))
{
if (g_os.IsWin9x())
g_ErrorLevel->Assign(HOTKEY_EL_WIN9X); // Reported even if the hotkey is disabled or suspended.
mModifiersLR = 0; // Since ModifierVK/SC is now its substitute.
}
// Update: This is no longer needed because the hook attempts to compensate.
// However, leaving it enabled may improve performance and reliability.
// Update#2: No, it needs to be disabled, otherwise alt-tab won't work right
// in the rare case where an ALT key itself is defined as "AltTabMenu":
//else
// It has no ModifierVK/SC and no modifiers, so it's a hotkey that is defined
// to fire only when the Alt-Tab menu is visible. Since the ALT key must be
// down for that menu to be visible (on all OSes?), add the ALT key to this
// keys modifiers so that it will be detected as a hotkey whenever the
// Alt-Tab menu is visible:
// modifiers |= MOD_ALT;
}
if (g_os.IsWin9x())
{
// Fix for v1.0.25: If no VK could be found, try to find one so that the attempt
// to register the hotkey will have a chance of success. This change is known to
// permit all the keys normally handled by scan code to work as hotkeys on Win9x.
// Namely: Del, Ins, Home, End, PgUp/PgDn, and the arrow keys.
// One of the main reasons for handling these keys by scan code is that
// they each have a counterpart on the Numpad, and in many cases a user would
// not want both to become hotkeys. It could be argued that this should be changed
// For Win NT/2k/XP too, but then it would have to be documented that the $ prefix
// would have to be used to prevent the "both keys of the pair" behavior. This
// confusion plus the chance of breaking existing scripts doesn't seem worth the
// small benefit of being able to avoid the keyboard hook in cases where these
// would be the only hotkeys using it. If there is ever a need, a new hotkey
// prefix or option can be added someday to handle this, perhaps #LinkPairedKeys
// to avoid having yet another reserved hotkey prefix/symbol.
if (mSC)
if (mVK = sc_to_vk(mSC)) // Might always succeed in finding a non-zero VK.
mSC = 0; // For maintainability, to force it to be one type or the other.
// v1.0.42 (see comments at "mModifiersLR" further below):
if (mModifiersLR)
{
mModifiers |= ConvertModifiersLR(mModifiersLR); // Convert L/R modifiers into neutral ones for use with RegisterHotkey().
mModifiersLR = 0;
}
}
else // Not Win9x.
{
if (HK_TYPE_CAN_BECOME_KEYBD_HOOK(mType)) // Added in v1.0.39 to make a hotkey such as "LButton & LCtrl" install the mouse hook.
{
switch (mVK)
{
// I had trouble finding this before, so this comment serves as a means of finding the place
// where a hotkey with a non-zero mSC is set to type HK_KEYBD_HOOK (Win9x converts mSC to mVK higher above).
case 0: // Scan codes having no available virtual key must always be handled by the hook.
// In addition, to support preventing the toggleable keys from toggling, handle those
// with the hook also. But for Win9x(), attempt to register such hotkeys by not requiring the hook
// (due to checks above, this section doesn't run for Win9x). This provides partial functionality
// on Win9x, the limitation being that the key will probably be toggled to its opposite state when
// it's used as a hotkey. But the user may be able to concoct a script workaround for that.
case VK_NUMLOCK:
case VK_CAPITAL:
case VK_SCROLL:
// When the AppsKey used as a suffix, always use the hook to handle it because registering
// such keys with RegisterHotkey() will fail to suppress(hide) the key-up events from the system,
// and the key-up for Apps key, at least in apps like Explorer, is a special event that results in
// the context menu appearing (though most other apps seem to use the key-down event rather than
// the key-up, so they would probably work okay). Note: Of possible future use is the fact that
// if the Alt key is held down before pressing Appskey, it's native function does
// not occur. This may be similar to the fact that LWIN and RWIN don't cause the
// start menu to appear if a shift key is held down. For Win9x, leave it as a registered
// hotkey (as set higher above) it in case its limited capability is useful to someone.
case VK_APPS:
// Finally, the non-neutral (left-right) modifier keys (except LWin and RWin) must also
// be done with the hook because even if RegisterHotkey() claims to succeed on them,
// I'm 99% sure I tried it and the hotkeys don't actually work with that method:
case VK_LCONTROL: // But for Win9x(), attempt to register such hotkeys by not requiring the
case VK_RCONTROL: // hook here. RegisterHotkey() probably doesn't fail on Win9x, these
case VK_LSHIFT: // L/RControl/Shift/Alt hotkeys don't actually function as hotkeys.
case VK_RSHIFT: // However, it doesn't seem worth adding code to convert to neutral since
case VK_LMENU: // that might break existing scripts.
case VK_RMENU:
mKeybdHookMandatory = true;
break;
// To prevent the Start Menu from appearing for a naked LWIN or RWIN, must
// handle this key with the hook (the presence of a normal modifier makes
// this unnecessary, at least under WinXP, because the Start Menu is
// never invoked when a modifier key is held down with lwin/rwin).
// But keep it NORMAL on Win9x (as set higher above) since there's a
// chance some people might find it useful, perhaps by doing their own
// workaround for the Start Menu appearing (via "Send {Ctrl}").
case VK_LWIN:
case VK_RWIN:
// If this hotkey is an unmodified modifier (e.g. Control::) and there
// are any other hotkeys that rely specifically on this modifier,
// have the hook handle this hotkey so that it will only fire on key-up
// rather than key-down. Note: cases where this key's modifiersLR or
// ModifierVK/SC are non-zero -- as well as hotkeys that use sc vs. vk
// -- have already been set to use the keybd hook, so don't need to be
// handled here. UPDATE: All the following cases have been already set
// to be HK_KEYBD_HOOK:
// - left/right ctrl/alt/shift (since RegisterHotkey() doesn't support them).
// - Any key with a ModifierVK/SC
// - The naked lwin or rwin key (due to the check above)
// Therefore, the only case left to be detected by this next line is the
// one in which the user configures the naked neutral key VK_SHIFT,
// VK_MENU, or VK_CONTROL. As a safety precaution, always handle those
// neutral keys with the hook so that their action will only fire
// when the key is released (thus allowing each key to be used for its
// normal modifying function):
// If it's Win9x, this section is never reached so the hotkey is not set to be
// HK_KEYBD_HOOK. This allows it to be registered as a normal hotkey in case
// it's of use to anyone.
case VK_CONTROL:
case VK_MENU:
case VK_SHIFT:
if (!mModifiers && !mModifiersLR) // Modifier key as suffix and has no modifiers (or only a ModifierVK/SC).
mKeybdHookMandatory = true;
//else keys modified by CTRL/SHIFT/ALT/WIN can always be registered normally because these
// modifiers are never used (are overridden) when that key is used as a ModifierVK
// for another key. Example: if key1 is a ModifierVK for another key, ^key1
// (or any other modified versions of key1) can be registered as a hotkey because
// that doesn't affect the hook's ability to use key1 as a prefix:
break;
}
} // if HK_TYPE_CAN_BECOME_KEYBD_HOOK(mType)
} // Not Win9x.
// Below section avoids requiring the hook on Win9x in some cases because that would effectively prevent any
// attempt to register such hotkeys, which might work, at least for the following cases:
// g_ForceKeybdHook (#UseHook): Seems best to ignore it on Win9x (which is probably the documented behavior).
// mNoSuppress: They're supported as normal (non-tilde) hotkeys as long as RegisterHotkey succeeds.
// mAllowExtraModifiers: v1.0.42: The hotkey is registered along with any explicit modifiers it might have
// (the wildcard part is ignored). In prior versions, wildcard hotkeys were disabled, and so were
// any non-wildcard suffixes they "eclipsed" (e.g. *^a would eclipse/disable ^!a). Both cases are
// now enabled in v1.0.42.
// mModifiersLR: v1.0.42: In older versions, such hotkeys were wrongly registered without any modifiers
// at all. For example, <#x would be registered simply as "x". To correct this, it seemed best
// to convert left/right modifiers (include lwin/rwin since RegisterHotkey requires the neutral "Win")
// to their neutral counterparts so that an attempt can be made to register them. This step is done
// in the code section above.
// mModifierVK or SC: Always set to use the hook so that they're disabled on Win9x, which seems best because
// there's no way to even get close to full functionality on Win9x.
// mVK && vk_to_sc(mVK, true): Its mVK corresponds to two scan codes, which would requires the hook
// (except Win9x). The hook is required because when the hook receives a NumpadEnter keystroke
// (regardless of whether NumpadEnter itself is also a hotkey), it will look up that scan code
// and see that it always and unconditionally takes precedence over its VK, and thus the VK is ignored.
// If that scan code has no hotkey, nothing happens (i.e. pressing NumpadEnter doesn't fire an Enter::
// hotkey; it does nothing at all unless NumpadEnter:: is also a hotkey). By contrast, if Enter
// became a registered hotkey vs. hook, it would fire for both Enter and NumpadEnter unless the script
// happened to have a NumpadEnter hotkey also (which we don't check due to rarity).
// aHookAction: In versions older than 1.0.42, most of these were disabled just because they happened
// to be mModifierVK/SC hotkeys, which are always flagged as hook and thus disabled in Win9x.
// However, ones that were implemented as <#x rather than "LWin & x" were inappropriately enabled.
// as a naked/unmodified "x" hotkey (due to mModifiersLR paragraph above, and the fact that
// aHookAction hotkeys really should be explicitly disabled since they can't function on Win9x.
// This disabling-by-setting-to-type-hook is now ALSO done in ManifestAllHotkeysHotstringsHooks() because
// the hotkey command is capable of changing a hotkey to/from the alt-tab type.
// mKeyUp: Disabled as an unintended/benevolent side-effect of older loop processing in
// ManifestAllHotkeysHotstringsHooks(). This is retained, though now it's made more explicit via below,
// for maintainability.
// Older Note: Do this for both AT_LEAST_ONE_VARIANT_HAS_TILDE and NO_SUPPRESS_PREFIX. In the case of
// NO_SUPPRESS_PREFIX, the hook is needed anyway since the only way to get NO_SUPPRESS_PREFIX in
// effect is with a hotkey that has a ModifierVK/SC.
if (HK_TYPE_CAN_BECOME_KEYBD_HOOK(mType))
if ( (mModifiersLR || aHookAction || mKeyUp || mModifierVK || mModifierSC) // mSC is handled higher above.
|| !g_os.IsWin9x() && (g_ForceKeybdHook || mAllowExtraModifiers // mNoSuppress&NO_SUPPRESS_PREFIX has already been handled elsewhere. Other bits in mNoSuppress must be checked later because they can change by any variants added after *this* one.
|| (mVK && !mVK_WasSpecifiedByNumber && vk_to_sc(mVK, true))) ) // Its mVK corresponds to two scan codes (such as "ENTER").
mKeybdHookMandatory = true;
// v1.0.38.02: The check of mVK_WasSpecifiedByNumber above was added so that an explicit VK hotkey such
// as "VK24::" (which is VK_HOME) can be handled via RegisterHotkey() vs. the hook. Someone asked for
// this ability, but even if it weren't for that it seems more correct to recognize an explicitly-specified
// VK as a "neutral VK" (i.e. one that fires for both scan codes if the VK has two scan codes). The user
// can always specify "SCnnn::" as a hotkey to avoid this fire-on-both-scan-codes behavior.
// Currently, these take precedence over each other in the following order, so don't
// just bitwise-or them together in case there's any ineffectual stuff stored in
// the fields that have no effect (e.g. modifiers have no effect if there's a mModifierVK):
v.mMaxThreads = g_MaxThreadsPerHotkey; // The values of these can vary during load-time.
v.mMaxThreadsBuffer = g_MaxThreadsBuffer; //
v.mHotCriterion = g_HotCriterion; // If this hotkey is an alt-tab one (mHookAction), this is stored but ignored until/unless the Hotkey command converts it into a non-alt-tab hotkey.
v.mHotWinTitle = g_HotWinTitle;
v.mHotWinText = g_HotWinText; // The value of this and other globals used above can vary during load-time.
v.mEnabled = true;
if (aSuffixHasTilde)
{
v.mNoSuppress = true; // Override the false value set by ZeroMemory above.
mNoSuppress |= AT_LEAST_ONE_VARIANT_HAS_TILDE;
// For simplicity, make the hook mandatory for any hotkey that has at least one non-suppressed variant.
// Otherwise, ManifestAllHotkeysHotstringsHooks() would have to do a loop to check if any
// non-suppressed variants are actually enabled & non-suspended to decide if the hook is actually needed
// for a hotkey that has a global variant. Due to rarity and code size, it doesn't seem worth it.
mKeybdHookMandatory = true;
}
else
mNoSuppress |= AT_LEAST_ONE_VARIANT_LACKS_TILDE;
// Update the linked list:
if (!mFirstVariant)
mFirstVariant = mLastVariant = vp;
else
{
mLastVariant->mNextVariant = vp;
// This must be done after the above:
mLastVariant = vp;
}
return vp; // Indicate success by returning the new object.
case '*': // On Win9x, an attempt will be made to register such hotkeys (ignoring the wildcard).
if (aThisHotkey)
aThisHotkey->mAllowExtraModifiers = true;
if (aProperties)
aProperties->has_asterisk = true;
break;
case '~':
if (aProperties)
aProperties->suffix_has_tilde = true; // If this is the prefix's tilde rather than the suffix, it will be overridden later below.
break;
case '$':
if (g_os.IsWin9x())
{
if (aThisHotkey)
aThisHotkey->mUnregisterDuringThread = true;
}
else
if (aThisHotkey)
aThisHotkey->mKeybdHookMandatory = true; // This flag will be ignored if TextToKey() decides this is a JOYSTICK or MOUSE hotkey.
// else ignore the flag and try to register normally, which in most cases seems better
// than disabling the hotkey.
break;
case '!':
if ((!key_right && !key_left))
{
modifiers |= MOD_ALT;
break;
}
// Both left and right may be specified, e.g. ><+a means both shift keys must be held down:
if (key_left)
{
modifiersLR |= MOD_LALT;
key_left = false;
}
if (key_right)
{
modifiersLR |= MOD_RALT;
key_right = false;
}
break;
case '^':
if ((!key_right && !key_left))
{
modifiers |= MOD_CONTROL;
break;
}
if (key_left)
{
modifiersLR |= MOD_LCONTROL;
key_left = false;
}
if (key_right)
{
modifiersLR |= MOD_RCONTROL;
key_right = false;
}
break;
case '+':
if ((!key_right && !key_left))
{
modifiers |= MOD_SHIFT;
break;
}
if (key_left)
{
modifiersLR |= MOD_LSHIFT;
key_left = false;
}
if (key_right)
{
modifiersLR |= MOD_RSHIFT;
key_right = false;
}
break;
case '#':
if ((!key_right && !key_left))
{
modifiers |= MOD_WIN;
break;
}
if (key_left)
{
modifiersLR |= MOD_LWIN;
key_left = false;
}
if (key_right)
{
modifiersLR |= MOD_RWIN;
key_right = false;
}
break;
default:
goto break_loop; // Stop immediately whenever a non-modifying char is found.
} // switch (*marker)
} // for()
break_loop:
// Now *marker is the start of the key's name. In addition, one of the following is now true:
// 1) marker[0] is a non-modifier symbol; that is, the loop stopped because it found the first non-modifier symbol.
// 2) marker[1] is '\0'; that is, the loop stopped because it reached the next-to-last char (the last char itself is never a modifier; e.g. ^+ is Ctrl+Plus on some keyboard layouts).
// 3) marker[1] is the start of the string " Up", in which case marker[0] is considered the suffix key even if it happens to be a modifier symbol (see comments at for-loop's control stmt).
if (aProperties)
{
// When caller passes non-NULL aProperties, it didn't omit the prefix portion of a composite hotkey
// (e.g. the "a & " part of "a & b" is present). So parse these and all other types of hotkeys when in this mode.
char *composite, *temp;
if (composite = strstr(marker, COMPOSITE_DELIMITER))
{
strlcpy(aProperties->prefix_text, marker, sizeof(aProperties->prefix_text)); // Protect against overflow case script ultra-long (and thus invalid) key name.
if (temp = strstr(aProperties->prefix_text, COMPOSITE_DELIMITER)) // Check again in case it tried to overflow.
omit_trailing_whitespace(aProperties->prefix_text, temp)[1] = '\0'; // Truncate prefix_text so that the suffix text is omitted.
if (aProperties->suffix_has_tilde = (*composite == '~')) // Override any value of suffix_has_tilde set higher above.
++composite; // For simplicity, no skipping of leading whitespace between tilde and the suffix key name.
strlcpy(aProperties->suffix_text, composite, sizeof(aProperties->suffix_text)); // Protect against overflow case script ultra-long (and thus invalid) key name.
}
else // A normal (non-composite) hotkey, so suffix_has_tilde was already set properly (higher above).
strlcpy(aProperties->suffix_text, omit_leading_whitespace(marker), sizeof(aProperties->suffix_text)); // Protect against overflow case script ultra-long (and thus invalid) key name.
if (temp = strcasestr(aProperties->suffix_text, " Up")) // Should be reliable detection method because leading spaces have been omitted and it's unlikely a legitmate key name will ever contain a space followed by "Up".
{
omit_trailing_whitespace(aProperties->suffix_text, temp)[1] = '\0'; // Omit " Up" from suffix_text since caller wants that.
aProperties->is_key_up = true; // Override the default set earlier.
if (*mReplacement && !mOmitEndChar) // The ending character (if present) needs to be sent too.
{
// Send the final character in raw mode so that chars such as !{} are sent properly.
// v1.0.43: Avoid two separate calls to SendKeys because:
// 1) It defeats the uninterruptibility of the hotstring's replacement by allowing the user's
// buffered keystrokes to take effect in between the two calls to SendKeys.
// 2) Performance: Avoids having to install the playback hook twice, etc.
char end_char;
if (mEndCharRequired && (end_char = (char)LOWORD(alParam))) // Must now check mEndCharRequired because LOWORD has been overloaded with context-sensitive meanings.
sprintf(start_of_replacement, "%s%c", mSendRaw ? "" : "{Raw}", (char)end_char); // v1.0.43.02: Don't send "{Raw}" if already in raw mode!
}
}
if (!*SendBuf) // No keys to send.
return;
// For the following, mSendMode isn't checked because the backup/restore is needed to varying extents
// by every mode.
int old_delay = g.KeyDelay;
int old_press_duration = g.PressDuration;
int old_delay_play = g.KeyDelayPlay;
int old_press_duration_play = g.PressDurationPlay;
g.KeyDelay = mKeyDelay; // This is relatively safe since SendKeys() normally can't be interrupted by a new thread.
g.PressDuration = -1; // Always -1, since Send command can be used in body of hotstring to have a custom press duration.
g.KeyDelayPlay = -1;
g.PressDurationPlay = mKeyDelay; // Seems likely to be more useful (such as in games) to apply mKeyDelay to press duration rather than above.
// v1.0.43: The following section gives time for the hook to pass the final keystroke of the hotstring to the
// system. This is necessary only for modes other than the original/SendEvent mode because that one takes
// advantage of the serialized nature of the keyboard hook to ensure the user's final character always appears
// on screen before the replacement text can appear.
// By contrast, when the mode is SendPlay (and less frequently, SendInput), the system and/or hook needs
// another timeslice to ensure that AllowKeyToGoToSystem() actually takes effect on screen (SuppressThisKey()
// doesn't seem to have this problem).
if (!(mDoBackspace || mOmitEndChar) && mSendMode != SM_EVENT) // The final character of the abbreviation (or its EndChar) was not suppressed by the hook.
Sleep(0);
SendKeys(SendBuf, mSendRaw, mSendMode); // Send the backspaces and/or replacement.
g.KeyDelay = old_delay; // Restore original values.
return g_script.ScriptError(ERR_OUTOFMEM); // Short msg. since so rare.
shs = (Hotstring **)realloc_temp;
sHotstringCountMax += HOTSTRING_BLOCK_SIZE;
}
if ( !(shs[sHotstringCount] = new Hotstring(aJumpToLabel, aOptions, aHotstring, aReplacement, aHasContinuationSection)) )
return g_script.ScriptError(ERR_OUTOFMEM); // Short msg. since so rare.
if (!shs[sHotstringCount]->mConstructedOK)
{
delete shs[sHotstringCount]; // SimpleHeap allows deletion of most recently added item.
return FAIL; // The constructor already displayed the error.
}
++sHotstringCount;
mAtLeastOneEnabled = true; // Added in v1.0.44. This method works because the script can't be suspended while hotstrings are being created (upon startup).